/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2;
import lights.interfaces.*;
import lights.adapters.*;

import java.net.InetAddress;
import java.util.*;

/**
 * A special tuplespace storing (system) context information. The LimeSystemTupleSpace works as a local tuplespace for
 * users; nevertheless the Lime system can exploit it as a federated tuplespace (.. this could be useful for fixing
 * communication related consistency problems).
 * <br>
 * The LimeSystemTupleSpace is kept up-to-date with the lower-level representation of the engaged hosts which is 
 * "within" the CA layer. We could have used a specific ITupleSpaceProxy onto this low-level representation but we 
 * preferred instead the benefits of having a "real" underlying tuplespace engine. 
 * <br>
 * The format of the non-limified tuples within the LimeSystemTupleSpace is:
 * <P>
 * &#9679 <i>{HOST, LimeServerID}</i> for engaged hosts (tuple) 
 * &#9679 <i>{HOST_GONE, LimeServerID}</i> for hosts that are disengaged (anti-tuple) 
 * </P>
 * Please note that the use of anti-tuples allows to fire reactions on removals of tuples (.. insertion of the 
 * corresponding anti-tuples). We don't publish information about tuplespaces within the LimeSystemTupleSpace since 
 * for the vast majority of applications the tuplespaces are known at design time.. also it is always possible to use
 * an "index/white-pages" tuplespace. 
 * <br>
 * When an host is disengaged/disengages its LimeSystemTupleSpace is/becomes empty. 
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

public class LimeSystemTupleSpace 
{	
 /** System tuple tag. */	
 public static final String HOST = "_host";
 
 /** System anti-tuple tag. */
 public static final String HOST_GONE = "_host_gone";
	
 // the LimeSystemTupleSpace singleton	
 private static LimeSystemTupleSpace lsts = new LimeSystemTupleSpace();	
	
 // this (long) global counter is used to enforce uniqueness of the numeric identifiers for the TupleIDs
 private static long tids = -1;

 // the LocalOpMgr of the LimeSystemTupleSpace
 private LocalOpMgr lop;
 
 // private constructor
 private LimeSystemTupleSpace() 
 {
  super();
  // creates and starts the LocalOpMgr
  lop = new LocalOpMgr(LimeConstants.SYSTEM_NAME, LimeConstants.SYSTEM_POLICY);
  lop.start();
  // joins group (.. if there is a "system" group !) 
  if (LimeServer.getServer().isEngaged()) CommunicationMgr.get().getCA().joinGroup(LimeConstants.SYSTEM_NAME);
 }
 
 public static boolean isEngaged(Location location) {
		ITuple t = new Tuple();
		t.add((new Field()).setValue(HOST));
		t.add(new Field().setValue(location.getID()));
		LimeTuple ret = lsts.rdp(t);
		if (ret != null)
			return true;
		else
			return false;
	}
 /**
  * Returns the LimeSystemTupleSpace singleton. The LimeServer must be already booted, otherwise a BootException is 
  * thrown. 
  * 
  * @return LimeSystemTupleSpace
  * @throws BootException
  */ 
 public static LimeSystemTupleSpace get() 
 {
  if (!LimeServer.getServer().isBooted()) throw new BootException("server has not been booted");	
  return lsts;
 }
  
 /**
  * Returns the next TupleID for this host. Package-visible: see LimeTupleSpace.
  *  
  * @return valid TupleID
  */
 synchronized static TupleID nextTID() 
 {
  tids++;	
  return new TupleID(Location.HERE.getID(), tids);	
 } 

 /**
  * Reads a tuple matching a specific tuple-only template.
  * 
  * @param t tuple template 
  * @return LimeTuple
  */
 public LimeTuple rd(ITuple t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  return rd(new LimeTemplate(Location.HERE, Location.HERE, t));	
 }
 
 /**
  * Reads a tuple matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 LimeTuple rd(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
  
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RD, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 }    
 
 /**
  * Non-blocking read of a tuple matching a specific tuple-only template.
  * 
  * @param t tuple template
  * @return LimeTuple
  */
 public LimeTuple rdp(ITuple t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
 
  return rdp(new LimeTemplate(Location.HERE, Location.HERE, t));
 }
 
 /**
  * Non-blocking read of a tuple matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 LimeTuple rdp(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RDP, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 } 
 
 /**
  * Non blocking read of a group of tuples matching a specific tuple-only template.
  * 
  * @param t tuple template
  * @return array of LimeTuples
  */
 public LimeTuple[] rdg(ITuple t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 
  return rdg(new LimeTemplate(Location.HERE, Location.HERE, t));
 }
 
 /**
   * Non-blocking read of a group of tuples matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
   *  
   * @param t LimeTemplate
   * @return array of LimeTuples
   */ 
 LimeTuple[] rdg(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.RDG, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  ITuple[] ret = (ITuple[]) result.get();
  if (ret == null) return null; 
  LimeTuple[] ltret = new LimeTuple[ret.length];
  for (int i = 0; i < ret.length; i++) ltret[i] = LimeTuple.delimify(ret[i]);
  return ltret;
 }  
    
 /** 
  * Takes a tuple matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */
 LimeTuple in(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.IN, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());	
 }  
   
 /**
  * Non-blocking take of a tuple matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
  * 
  * @param t LimeTemplate
  * @return LimeTuple
  */   
 LimeTuple inp(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.INP, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  return LimeTuple.delimify((ITuple) result.get());
 }    
 
 /** 
  * Non-blocking take of a group of tuples matching a specific LimeTemplate. Package-visible: see LimeTupleSpace.
  * 
  * @param t LimeTemplate
  * @return array of LimeTuples
  */
 LimeTuple[] ing(LimeTemplate t)
 {
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
 	
  Result result = new Result();
  TupleOp to = new TupleOp(LimeConstants.ING, new ITuple[] {t.limify()}, result);
  lop.postOperation(to);
  ITuple[] ret = (ITuple[]) result.get();
  if (ret == null) return null; 
  LimeTuple[] ltret = new LimeTuple[ret.length];
  for (int i = 0; i < ret.length; i++) ltret[i] = LimeTuple.delimify(ret[i]);
  return ltret;
 }  
 
 /**
  * Out of a tuple. The destination is this host. Package-visible: see LimeTupleSpace.
  * 
  * @param t ITuple (not limified, just a normal ITuple)
  * @return corresponding LimeTuple
  */ 
 LimeTuple out(ITuple t)
 {	
  // parameter checking	
  if (t == null) throw new IllegalArgumentException("null value");
 	
  LimeTuple lt = new LimeTuple(nextTID(), Location.HERE, Location.HERE, t);	
  TupleOp to = new TupleOp(LimeConstants.OUT, new ITuple[] {lt.limify()}, new Result());
  lop.postOperation(to);
  return lt;
 } 
  
 /**
  * Out of a tuple for a specific destination Location. Package-visible: see LimeTupleSpace.
  * 
  * @param t ITuple (not limified, just a normal ITuple)
  * @param whereto destination Location
  * @return corresponding LimeTuple
  * @throws InvalidLocationException
  */ 
 LimeTuple out(Location whereto, ITuple t) 
 {
  // parameter checking	
  if (whereto == null || t == null) throw new IllegalArgumentException("null value");	
  if (whereto.isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");		

  LimeTuple lt = new LimeTuple(nextTID(), Location.HERE, whereto, t);	
  TupleOp to = new TupleOp(LimeConstants.OUT, new ITuple[] {lt.limify()}, new Result());
  lop.postOperation(to);
  return lt;
 }  

 /**  
  * Out of a group of tuples for a specific destination Location. Package-visible: see LimeTupleSpace.
  * 
  * @param t the array of ITuples (not limified, just normal ITuples)
  * @param whereto destination Location
  * @return corresponding LimeTuples
  * @throws InvalidLocationException
  */ 
 LimeTuple[] outg(Location whereto, ITuple[] t)
 {
  // parameter checking	
  if (whereto == null || testNullArray(t)) throw new IllegalArgumentException("null value"); 	
  if (whereto.isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");	
 
  LimeTuple[] lt = new LimeTuple[t.length];
  ITuple[] lim = new ITuple[t.length]; 
  for (int i = 0; i < t.length; i++)
  { 
   lt[i] = new LimeTuple(nextTID(), Location.HERE, whereto, t[i]); 
   lim[i] = lt[i].limify();  
  } 
  TupleOp to = new TupleOp(LimeConstants.OUTG, lim, new Result());
  lop.postOperation(to);
  return lt;
 }   
  
 /**  
  * Out of groups of tuples for specific destination Locations. Package-visible: see LimeTupleSpace.
  * 
  * @param t different groups of ITuples (not limified, just normal ITuples)
  * @param whereto destinations for the groups of ITuples
  * @return corresponding LimeTuples
  * @throws InvalidLocationException
  */ 
 LimeTuple[] outg(Location[] whereto, ITuple[][] t)
 {
  // parameter checking	
  if (testNullArray(whereto) || testNullArray(t)) throw new IllegalArgumentException("null value"); 	
  if (whereto.length != t.length) throw new IllegalArgumentException("lenghts of the given arrays must be the same");
  for (int z = 0; z < t.length; z++)
  {
   if (whereto[z].isUnspecified()) throw new InvalidLocationException("destination cannot be unspecified");	
   if (testNullArray(t[z])) throw new IllegalArgumentException("null value for tuple or array of tuples"); 
  } 
   
  int n = 0;
  for (int z = 0; z < t.length; z++) n += t[z].length;
  LimeTuple[] lt = new LimeTuple[n];
  ITuple[] lim = new ITuple[n]; 
  int k = -1;
  for (int j = 0; j < t.length; j++)
  for (int i = 0; i < t[j].length; i++)
  { 
   k++;	
   lt[k] = new LimeTuple(nextTID(), Location.HERE, whereto[j], t[j][i]); 
   lim[k] = lt[k].limify();  
  } 
  
  TupleOp to = new TupleOp(LimeConstants.OUTG, lim, new Result());
  lop.postOperation(to);
  return lt;
 }   
 
 /**
  * Installs strong reactions; a strong reaction cannot be remote or ubiquitous.
  * 
  * @param reactions strong reactions to install
  * @return corresponding reaction ids
  * @throws InvalidLocationException
  */ 
 public ReactionID[] addStrongReaction(Reaction[] reactions)
 {
  // parameter checking	
  if (testNullArray(reactions)) throw new IllegalArgumentException("null value");	
  for (int i = 0; i < reactions.length; i++) if (!reactions[i].getLimeTemplate().getCurrent().isHere()) throw new InvalidLocationException("strong reactions cannot be remote or ubiquitous"); 

  // end of check
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.INSTALL_STRONG, reactions, result);	
  lop.postOperation(ro);
  return (ReactionID[]) result.get();
 } 
 
 /**
  * Removes strong reactions; the ReactionIDs must correspond to strong reactions installed previously.
  * 
  * @param ids of the (registered) reactions to remove
  * @return boolean[], where <code>boolean[i]</code> is <code>true</code> if reaction <code>i</code> was successfully
  *         removed, <code>false</code> otherwise
  */ 
 public boolean[] removeStrongReaction(ReactionID[] ids) 
 {
  // parameter checking	
  if (testNullArray(ids)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.REMOVE_STRONG, ids, result);	
  lop.postOperation(ro);
  return (boolean[]) result.get();
 }  
 
 /**
  * Installs weak reactions on this host. Package-visible: see LimeTupleSpace.
  * 
  * @param reactions weak reactions to install
  * @return corresponding reaction ids
  */ 
 ReactionID[] addWeakReaction(Reaction[] reactions)
 {
  // parameter checking	
  if (testNullArray(reactions)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result();
  ReactionOp ro = new ReactionOp(LimeConstants.INSTALL_WEAK, reactions, result);
  lop.postOperation(ro);
  return (ReactionID[]) result.get();
 } 
 
 /**
  * Removes weak reactions from this host. Package-visible: see LimeTupleSpace.
  * 
  * @param ids of the (registered) reactions to remove
  * @return boolean[], where <code>boolean[i]</code> is <code>true</code> if reaction <code>i</code> was successfully
  *         removed, <code>false</code> otherwise
  */ 
 boolean[] removeWeakReaction(ReactionID[] ids) 
 {
  // parameter checking	
  if (testNullArray(ids)) throw new IllegalArgumentException("null value");	
 	
  Result result = new Result(); 	
  ReactionOp ro = new ReactionOp(LimeConstants.REMOVE_WEAK, ids, result);	
  lop.postOperation(ro);
  return (boolean[]) result.get();
 }  

 /**
  * Updates the LimeSystemTupleSpace on the basis of the arriving/leaving hosts. This method must be exposed to CAs.
  * 
  * @param deltaEngaged Hashtable of hosts that engaged since the last update
  * @param deltaDisengaged Hashtable of hosts that disengaged since the last update
  */
 public static void update(Hashtable deltaEngaged, Hashtable deltaDisengaged)
 {
  for (Enumeration e = deltaEngaged.keys(); e.hasMoreElements();)
  {
   LimeServerID aux = (LimeServerID) e.nextElement();	
   lsts.inp(new LimeTemplate(Location.HERE, Location.HERE, new Tuple().add(new Field().setValue(HOST_GONE)).add(new Field().setValue(aux))));
   lsts.out(new Tuple().add(new Field().setValue(HOST)).add(new Field().setValue(aux)));
   Reconciliator.welcome(aux);
  }
     
  for (Enumeration e = deltaDisengaged.keys(); e.hasMoreElements();)
  {
   LimeServerID aux = (LimeServerID) e.nextElement();	
   lsts.inp(new LimeTemplate(Location.HERE, Location.HERE, new Tuple().add(new Field().setValue(HOST)).add(new Field().setValue(aux))));
   lsts.out(new Tuple().add(new Field().setValue(HOST_GONE)).add(new Field().setValue(aux)));
   Reconciliator.away(aux);
  }
 }

 /**
  * Clears the LimeSystemTupleSpace. This method must be exposed to CAs.
  */
 public static void clear()
 {
  LimeTuple[] tus = lsts.ing(new LimeTemplate(Location.HERE, Location.HERE, new Tuple().add(new Field().setType(String.class)).add(new Field().setType(LimeServerID.class))));	
  Reconciliator.clear();
 }
 
 /**
  * This method is provided only because of LimeHTTP. Package-visible: see LimeTupleSpace.
  *
  * @return textual representation of the reactions 
  */
 String[] getTextualRegisteredReactions()
 {
  return lop.getTextualRegisteredReactions();
 } 

 // parameter checking function: returns true if the given array is null or contains at least one null element
 private static boolean testNullArray(Object[] o)
 {
  if (o == null) return true;
  for (int i = 0; i < o.length; i++) if (o[i] == null) return true;
  return false;
 }
 
}